classdef EncodingScheme < handle
    
    % Copyright (C) 2013, Georgia Tech Research Corporation
    % see the LICENSE file included with this software
    
    % Copied and modified from Coursera's assignments-api-examples.zip
    
    properties
        
    end
    
    methods (Static)
        
        % =========================== Base64 Encoder ============================
        % Thanks to Peter John Acklam
        %
        
        function y = encode_in_base64(x, eol)
            %BASE64ENCODE Perform base64 encoding on a string.
            %
            %   BASE64ENCODE(STR, EOL) encode the given string STR.  EOL is the line ending
            %   sequence to use; it is optional and defaults to '\n' (ASCII decimal 10).
            %   The returned encoded string is broken into lines of no more than 76
            %   characters each, and each line will end with EOL unless it is empty.  Let
            %   EOL be empty if you do not want the encoded string broken into lines.
            %
            %   STR and EOL don't have to be strings (i.e., char arrays).  The only
            %   requirement is that they are vectors containing values in the range 0-255.
            %
            %   This function may be used to encode strings into the Base64 encoding
            %   specified in RFC 2045 - MIME (Multipurpose Internet Mail Extensions).  The
            %   Base64 encoding is designed to represent arbitrary sequences of octets in a
            %   form that need not be humanly readable.  A 65-character subset
            %   ([A-Za-z0-9+/=]) of US-ASCII is used, enabling 6 bits to be represented per
            %   printable character.
            %
            %   Examples
            %   --------
            %
            %   If you want to encode a large file, you should encode it in chunks that are
            %   a multiple of 57 bytes.  This ensures that the base64 lines line up and
            %   that you do not end up with padding in the middle.  57 bytes of data fills
            %   one complete base64 line (76 == 57*4/3):
            %
            %   If ifid and ofid are two file identifiers opened for reading and writing,
            %   respectively, then you can base64 encode the data with
            %
            %      while ~feof(ifid)
            %         fwrite(ofid, base64encode(fread(ifid, 60*57)));
            %      end
            %
            %   or, if you have enough memory,
            %
            %      fwrite(ofid, base64encode(fread(ifid)));
            %
            %   See also BASE64DECODE.
            
            %   Author:      Peter John Acklam
            %   Time-stamp:  2004-02-03 21:36:56 +0100
            %   E-mail:      pjacklam@online.no
            %   URL:         http://home.online.no/~pjacklam
            
            if isnumeric(x)
                x = num2str(x);
            end
            
            % make sure we have the EOL value
            if nargin < 2
                eol = sprintf('\n');
            else
                if sum(size(eol) > 1) > 1
                    error('EOL must be a vector.');
                end
                if any(eol(:) > 255)
                    error('EOL can not contain values larger than 255.');
                end
            end
            
            if sum(size(x) > 1) > 1
                error('STR must be a vector.');
            end
            
            x   = uint8(x);
            eol = uint8(eol);
            
            ndbytes = length(x);                 % number of decoded bytes
            nchunks = ceil(ndbytes / 3);         % number of chunks/groups
            nebytes = 4 * nchunks;               % number of encoded bytes
            
            % add padding if necessary, to make the length of x a multiple of 3
            if rem(ndbytes, 3)
                x(end+1 : 3*nchunks) = 0;
            end
            
            x = reshape(x, [3, nchunks]);        % reshape the data
            y = repmat(uint8(0), 4, nchunks);    % for the encoded data
            
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Split up every 3 bytes into 4 pieces
            %
            %    aaaaaabb bbbbcccc ccdddddd
            %
            % to form
            %
            %    00aaaaaa 00bbbbbb 00cccccc 00dddddd
            %
            y(1,:) = bitshift(x(1,:), -2);                  % 6 highest bits of x(1,:)
            
            y(2,:) = bitshift(bitand(x(1,:), 3), 4);        % 2 lowest bits of x(1,:)
            y(2,:) = bitor(y(2,:), bitshift(x(2,:), -4));   % 4 highest bits of x(2,:)
            
            y(3,:) = bitshift(bitand(x(2,:), 15), 2);       % 4 lowest bits of x(2,:)
            y(3,:) = bitor(y(3,:), bitshift(x(3,:), -6));   % 2 highest bits of x(3,:)
            
            y(4,:) = bitand(x(3,:), 63);                    % 6 lowest bits of x(3,:)
            
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Now perform the following mapping
            %
            %   0  - 25  ->  A-Z
            %   26 - 51  ->  a-z
            %   52 - 61  ->  0-9
            %   62       ->  +
            %   63       ->  /
            %
            % We could use a mapping vector like
            %
            %   ['A':'Z', 'a':'z', '0':'9', '+/']
            %
            % but that would require an index vector of class double.
            %
            z = repmat(uint8(0), size(y));
            i =           y <= 25;  z(i) = 'A'      + double(y(i));
            i = 26 <= y & y <= 51;  z(i) = 'a' - 26 + double(y(i));
            i = 52 <= y & y <= 61;  z(i) = '0' - 52 + double(y(i));
            i =           y == 62;  z(i) = '+';
            i =           y == 63;  z(i) = '/';
            y = z;
            
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Add padding if necessary.
            %
            npbytes = 3 * nchunks - ndbytes;     % number of padding bytes
            if npbytes
                y(end-npbytes+1 : end) = '=';     % '=' is used for padding
            end
            
            if isempty(eol)
                
                % reshape to a row vector
                y = reshape(y, [1, nebytes]);
                
            else
                
                nlines = ceil(nebytes / 76);      % number of lines
                neolbytes = length(eol);          % number of bytes in eol string
                
                % pad data so it becomes a multiple of 76 elements
                y = [y(:) ; zeros(76 * nlines - numel(y), 1)];
                y(nebytes + 1 : 76 * nlines) = 0;
                y = reshape(y, 76, nlines);
                
                % insert eol strings
                eol = eol(:);
                y(end + 1 : end + neolbytes, :) = eol(:, ones(1, nlines));
                
                % remove padding, but keep the last eol string
                m = nebytes + neolbytes * (nlines - 1);
                n = (76+neolbytes)*nlines - neolbytes;
                y(m+1 : n) = '';
                
                % extract and reshape to row vector
                y = reshape(y, 1, m+neolbytes);
                
            end
            
            % output is a character array
            y = char(y);
            
        end
    end
    
end

